home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Pascal Super Library
/
Pascal Super Library (CW International)(1997).bin
/
BBS_UTL
/
BBSKIT31
/
MTASK20.ZIP
/
MTASK.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1990-03-15
|
13KB
|
515 lines
UNIT mtask;
{MTASK 2.0, a simple multi-tasker unit for Turbo Pascal 5.
Written in November, 1988, and donated to the public domain by:
Wayne E. Conrad
2627 North 51st Ave, #219
Phoenix, AZ 85035
BBS: (602) 484-9356, 300/1200/2400, 24 hours/day
This unit provides Turbo Pascal 5 with what I call "request driven"
multi-tasking. Switching from the current task to another task is done
whenever the current task requests a task switch by calling procedure
"switch_task." No interrupt driven context switching is done, because
it's a hassle.
See accompanying files for documentation and examples.}
{$F+} {Most procedures in this unit must be FAR}
INTERFACE
{Result codes. 0 is "no error"}
CONST
heap_full = 1; {Unable to allocate heap for the task's stack}
too_many_tasks = 2; {Maximum number of tasks are already running}
invalid_task_id = 3; {There is no task with that ID number}
{This is the procedure type for a task. The parent task can pass any
type of variable to the child task.}
TYPE
task_proc = PROCEDURE (VAR param);
{See the IMPLEMENTATION section for descriptions of these procedures and
functions.}
PROCEDURE create_task
(
task : task_proc;
VAR param ;
stack_size: Word;
VAR id : Word;
VAR result: Word
);
PROCEDURE terminate_task (id: Word; VAR result: Word);
PROCEDURE switch_task;
FUNCTION current_task_id: Word;
FUNCTION number_of_tasks: Word;
IMPLEMENTATION
{The maximum number of tasks. Modify to suit your needs.}
CONST
max_tasks = 16;
{This record contains all the information about a task, as follows:
stack_ptr: Saved stack segment (ss) and stack pointer (sp) registers
stack_org: If the stack is stored on the heap, this is the address of
the beginning of the block of memory allocated for the stack.
stack_bytes: Size of stack on the heap, or 0 if the stack is not on the
heap. If the stack is not on the heap, then this field is 0.
bp: Saved value of base pointer (BP) register.
id: The id number of the task
Note that DS (Data Segment register) is not stored. We can get away with
this by assuming that all tasks will use the same data segment.}
TYPE
task_rec =
RECORD
stack_ptr : Pointer;
stack_org : Pointer;
stack_bytes: Word;
bp : Word;
id : Word;
END;
{The number of tasks in the system}
VAR
ntasks: Word;
{Information for each task.}
VAR
task_info: ARRAY [1..max_tasks] OF task_rec;
{The last task ID assigned. If we haven't rolled the id's over, then
this allows us to assign task ID's without checking to see what id's have
been assigned.}
VAR
last_id : Word;
id_rollover: Boolean;
{This is the task number of the currently executing task}
VAR
current_task: Word;
{This is the record type of the initial contents of the stack when a task
is created. When the task is first switched to, it will be from within
the switch_task, terminate_task, or terminate_current_task procedure. At
the end of switch_task, BP will be popped, then a far return will be
done. The far return will transfer to the beginning of task. The task
can access the parameter "task_param," which is a pointer to whatever
data structure that the creator of this task wanted to pass to the new
task. When the task finally exits, a far return to "end_task" will be
done. The exception is the main task, which ends the program completely
if it exits.}
TYPE
initial_stack_rec_ptr = ^initial_stack_rec;
initial_stack_rec =
RECORD
bp : Word;
task_addr : task_proc;
end_task : Pointer;
task_param: Pointer;
END;
{Given a task ID, return the task number, or 0 if there is no task with
that ID.}
FUNCTION find_task (target_id: Word): Word;
VAR
n: Word;
BEGIN
n := 1;
WHILE (n <= ntasks) AND (task_info [n].id <> target_id) DO
Inc (n);
IF (n > ntasks) THEN
n := 0;
find_task := n
END;
{Remove a task's information from the task info array, and decrement the
number of tasks.}
PROCEDURE delete_task_info (task_num: Word);
VAR
i: Word;
BEGIN
FOR i := task_num TO ntasks - 1 DO
task_info [i] := task_info [i + 1];
Dec (ntasks)
END;
{Terminate the current task. If the current task is the only task, then
the program is halted. If the current task's stack was allocated from
the heap, it is freed.}
PROCEDURE terminate_current_task;
{These are defined as constants to force them into the data segment.
They can't be local, because local variables are stored on the stack and
we're going to switch to a different task (and therefore to a different
stack) before we're done with these variables.}
CONST
old_stack_org : Pointer = NIL;
old_stack_bytes: Word = 0;
VAR
task_num : Word;
new_stack: Pointer;
new_bp : Word;
BEGIN {terminate_current_task}
{If we're the last task left, then exit to DOS}
IF ntasks <= 1 THEN
Halt;
{Remember where the task's stack is so that we can free it up if it's
on the heap. We can't free it now, because we're still using it!}
WITH task_info [current_task] DO
BEGIN
old_stack_org := stack_org;
old_stack_bytes := stack_bytes
END;
{Remove the task's information from the task info array}
delete_task_info (current_task);
IF current_task > ntasks THEN
current_task := 1;
{Switch to the next task. The stack_ptr and bp are transfered into
local variables because it's much easier to access simple variables in
INLINE code than it is to access array variables.}
WITH task_info [current_task] DO
BEGIN
new_stack := stack_ptr;
new_bp := bp
END;
INLINE
(
$8b/$86/>new_stack+0/ {MOV AX,[BP].NEW_STACK+0}
$8b/$96/>new_stack+2/ {MOV DX,[BP].NEW_STACK+2}
$8b/$ae/>new_bp/ {MOV BP,[BP].NEW_BP}
$fa/ {CLI}
$8e/$d2/ {MOV SS,DX}
$8b/$e0/ {MOV SP,AX}
$fb {STI}
);
{If the task we just got rid of had its heap on the stack, then release
that memory back to the free pool.}
IF old_stack_bytes > 0 THEN
FreeMem (old_stack_org, old_stack_bytes)
END;
{Terminate a task. If task_id is 0, then the current task is deleted.
Possible result codes are:
0 No error
invalid_task_id There is no task with that ID number}
PROCEDURE terminate_task (id: Word; VAR result: Word);
{Delete a task. Do not use to delete the current task!}
PROCEDURE delete_task (task_num: Word);
BEGIN
WITH task_info [task_num] DO
IF stack_bytes > 0 THEN
FreeMem (stack_org, stack_bytes);
delete_task_info (task_num);
IF current_task > task_num THEN
Dec (current_task)
END;
VAR
task_num: Word;
BEGIN {terminate_task}
result := 0;
IF id = 0 THEN
terminate_current_task
ELSE
BEGIN
task_num := find_task (id);
IF task_num = 0 THEN
result := invalid_task_id
ELSE
IF task_num = current_task THEN
terminate_current_task
ELSE
delete_task (task_num)
END
END;
{Create a new task and pass parameter "param" to it. Stack space for the
task is allocated from the heap, and the stack is initialized so that
procedure "new_task" will be executed with parameter "param". Result
codes are:
0 No error occured
heap_full Unable to allocate heap for the task's stack
too_many_tasks Maximum number of tasks are already running
If an error occurs, then id is not set. Otherwise, id is the task id of
the newly created task.}
PROCEDURE create_task
(
task : task_proc;
VAR param ;
stack_size: Word;
VAR id : Word;
VAR result: Word
);
{This is the task number of the task we're creating}
VAR
task_num: Word;
{Allocate stack space for the task. The minimum allowable requested
stack size is 512 bytes. For some reason, the stack-check procedure in
Turbo's run-time library has that limit hard-coded into it.
stack_org is set to the address of the beginning of the block of memory
allocated for the stack.
stack_bytes is set to the size of the block of memory allocated for the
stack.}
PROCEDURE create_stack;
BEGIN
IF stack_size < 512 THEN
stack_size := 512;
IF stack_size > MaxAvail THEN
result := heap_full
ELSE
WITH task_info [task_num] DO
BEGIN
GetMem (stack_org, stack_size);
stack_bytes := stack_size
END
END;
{Initialize the stack and the stack pointer. The structure
"initial_stack_rec" is placed at the top of the stack area, with the
stack pointer pointing to its lowest element. See the comments for
initial_stack_rec for what the stuff in initial_stack_rec actually
does.}
PROCEDURE init_stack;
VAR
stack_ofs: Word;
BEGIN
WITH task_info [task_num] DO
BEGIN
stack_ofs := Ofs (stack_org^) + stack_bytes - Sizeof (initial_stack_rec);
stack_ptr := Ptr (Seg (stack_org^), stack_ofs);
bp := Ofs (stack_ptr^);
WITH initial_stack_rec_ptr (stack_ptr)^ DO
BEGIN
task_param := @param;
task_addr := task;
end_task := @terminate_current_task;
bp := 0
END
END
END;
{Find an unused task id and assign it to the new task}
PROCEDURE assign_task_id;
{Increment "last_id" to (hopefully) turn it into the task_id we're
going to assign. If it rolls over, set it to 2 (task 1 will always
exist, since it's the root task) and remember that we've rolled
over.}
PROCEDURE increment_last_id;
BEGIN
IF last_id = 65535 THEN
BEGIN
last_id := 2;
id_rollover := True
END
ELSE
Inc (last_id)
END;
BEGIN {assign_task_id}
increment_last_id;
IF id_rollover THEN
WHILE (find_task (last_id) <> 0) DO
increment_last_id;
id := last_id;
task_info [task_num].id := id
END;
BEGIN {create_task}
result := 0;
IF ntasks >= max_tasks THEN
result := too_many_tasks
ELSE
BEGIN
task_num := Succ (ntasks);
create_stack;
IF result = 0 THEN
BEGIN
init_stack;
assign_task_id;
Inc (ntasks)
END
END
END;
{Switch to the next task}
PROCEDURE switch_task;
VAR
new_stack: Pointer;
old_bp : Word;
new_bp : Word;
BEGIN
{Only switch if there are other tasks to switch to}
IF ntasks > 1 THEN
BEGIN
{Save the current value of SS, SP, and BP for this task}
INLINE
(
$89/$ae/>old_bp {MOV OLD_BP,BP}
);
WITH task_info [current_task] DO
BEGIN
stack_ptr := Ptr (Sseg, Sptr);
bp := old_bp
END;
{Switch to the next task. The bit with new_stack and new_bp are
because it's easier to write INLINE code to access a simple variable
than it is to access a record of an array.}
IF current_task >= ntasks THEN
current_task := 1
ELSE
Inc (current_task);
WITH task_info [current_task] DO
BEGIN
new_stack := stack_ptr;
new_bp := bp
END;
INLINE
(
$8b/$86/>new_stack+0/ {MOV AX,[BP].NEW_STACK+0}
$8b/$96/>new_stack+2/ {MOV DX,[BP].NEW_STACK+2}
$8b/$ae/>new_bp/ {MOV BP,[BP].NEW_BP}
$Fa/ {CLI}
$8e/$d2/ {MOV SS,DX}
$8b/$e0/ {MOV SP,AX}
$fb {STI}
)
END
END;
{Return the id number of the currently executing task}
FUNCTION current_task_id: Word;
BEGIN
current_task_id := task_info [current_task].id
END;
{Return the number of tasks}
FUNCTION number_of_tasks: Word;
BEGIN
number_of_tasks := ntasks
END;
{Initialize this unit. The task list is initialized to contain the
current task, whose task id is 1.}
PROCEDURE init_mtask;
VAR
id: Word;
BEGIN
ntasks := 1;
current_task := 1;
WITH task_info [current_task] DO
BEGIN
stack_org := NIL;
stack_bytes := 0;
id := 1
END;
last_id := 1;
id_rollover := False
END;
BEGIN {mtask}
init_mtask
END.